package top.jacktgq.cannotModify.conn_pool;
/**
 * 
 * @Title: MyJdbcConnPoolWithSemaphore.java
 * @Package top.jacktgq.cannotModify 
 * @Description: 自定义支持多线程访问的数据库连接池
 * @author CandyWall   
 * @date 2020年11月3日 下午11:34:54 
 * @version V1.0
 */

import java.sql.Connection;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicIntegerArray;

import top.jacktgq.utils.LogUtils;
import top.jacktgq.utils.Sleeper;

public class MyJdbcConnPool {
	//1、连接池大小
	private final int poolSize;
	
	//2、连接对象数组
	private Connection[] conns;
	
	//3、连接状态数组 0表示空闲，1表示繁忙
	private AtomicIntegerArray states;
	
	//4、构造方法初始化
	public MyJdbcConnPool(int poolSize) {
		this.poolSize = poolSize;
		//初始化线程池数组
		conns = new Connection[poolSize];
		//初始化连接状态数组
		states = new AtomicIntegerArray(new int[poolSize]);
		//初始化连接池中的连接
		for(int i = 0; i < poolSize; i++) {
			conns[i] = new MockConnection();
		}
	}
	
	//5、获得连接
	public Connection getConn() {
		while(true) {
			for(int i = 0; i < conns.length; i++) {
				//获取空闲连接
				if(states.get(i) == 0) {
					if(states.compareAndSet(i, 0, 1)) {
						LogUtils.log("MyJdbcConnPool", "获取连接成功，" + "连接编号为：" + (i + 1));
						return conns[i];
					}
				}
			}
			//如果没有空闲连接，当前线程进入等待
			synchronized (states) {
				LogUtils.log("MyJdbcConnPool", "获取连接失败，进入等待...");
				try {
					states.wait();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}
	
	//6、归还连接
	public void free(Connection conn) {
		//判断归还的连接是否是连接池中的连接
		for(int i = 0; i < poolSize; i++) {
			if(conns[i] == conn) {
				//这里的修改值不需要使用cas进行同步，
				//一个线程在归还连接的时候，只有它自己有权限对这个连接的状态进行修改，其它线程则不享有这种权限
				states.set(i, 0);
				//当归还连接的时候就应该紧接着唤醒等待中的线程
				synchronized (states) {
					LogUtils.log("MyJdbcConnPool", "归还连接成功，" + "连接编号为：" + (i + 1));
					//唤醒因为没有空闲连接池而进入等待的线程
					states.notifyAll();
				}
				break;
			}
		}

		//唤醒等待中的线程的逻辑不应该放到循环外面，
		//如果循环一遍都发现归还的连接不属于这个线程池的时候，这时候就没有必要去唤醒其他线程了，
		//因为就算唤醒了，连接池中也没有因为这次归还而增加多余的空闲的连接
		/*synchronized (states) {
			//唤醒因为没有空闲连接池而进入等待的线程
			states.notifyAll();
		}*/
	}
	
	public static void main(String[] args) {
		MyJdbcConnPool connPool = new MyJdbcConnPool(3);
		for(int i = 0; i < 10; i++) {
			new Thread(() -> {
				//尝试去获取一个连接
				Connection conn = null;
				try {
					conn = connPool.getConn();
					Sleeper.sleep(new Random().nextInt(1000), TimeUnit.MILLISECONDS);
				} finally {
					connPool.free(conn);
				}
			}, "线程" + (i + 1)).start();
		}
	}
	
}